/* Copyright (C) 2008  Romolo Camplani, Marco Fortunato, Marco Marelli, Guido Rota, Fabio A. Schreiber et al.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.dei.perla.sys.device.channel.concrete.serial;

import java.io.IOException;
import java.util.TooManyListenersException;

import javax.comm.CommPortIdentifier;
import javax.comm.NoSuchPortException;
import javax.comm.PortInUseException;
import javax.comm.SerialPort;
import javax.comm.SerialPortEvent;
import javax.comm.SerialPortEventListener;
import javax.comm.UnsupportedCommOperationException;

import org.dei.perla.sys.device.channel.Channel;
import org.dei.perla.sys.device.channel.frame.address.concrete.SerialFrameAddress;
import org.dei.perla.sys.device.channel.frame.receiver.FrameReceiverHeaderLess;
import org.dei.perla.sys.device.channel.frame.sender.FrameSenderHeaderLess;
import org.dei.perla.utils.logger.LogRecord.Type;
import org.dei.perla.utils.logger.LogRecord.Verbosity;


/**
 * Canale seriale headerless.
 */
public class SerialChannel extends Channel {

	/**
	 * Porta seriale aperta
	 */
	private SerialPort pSerialPort;
	
	/**
	 * Gestore eventi porta seriale
	 */
	private SerialPortEventListener pSerialPortListener;
	
	/**
	 * Configurazione canale seriale
	 */
	private SerialChannelConfiguration pConfiguration;
	
	/**
	 * Inizializza un canale su seriale headerless
	 * 
	 * @param parName
	 * @param parFrameReceiver
	 * @param parFrameSender
	 * @param parConfiguration
	 */
	public SerialChannel(String parName, FrameReceiverHeaderLess parFrameReceiver,
			FrameSenderHeaderLess parFrameSender, SerialChannelConfiguration parConfiguration) {
		
		super(parName, parFrameReceiver, parFrameSender, null);
		this.pConfiguration = parConfiguration;
		
		this.pLocalAddress 	= new SerialFrameAddress(this.pConfiguration.getPortName());
		this.pRemoteAddress = this.pLocalAddress;
		
	}
	
	/**
	 * Funzione di apertura del canale
	 */
	protected void openChannel() {
		
		// Indica se si son verificati errori
		boolean tOk = true;			
		
		// Identificatore della porta seriale da aprire
		CommPortIdentifier tSerialPortIdentifier = null;		
		
		// Stringa descrittiva con i parametri della porta seriale
		String tParamString = "";		
		
		// Imposta il tipo di canale
		this.setEventBasedInput(false);

		// Aggiunge una notifica nel log
		this.loggerNotify(Type.Information, "Apertura porta <" + this.pConfiguration.getPortName() + "> in corso", Verbosity.High);
		
		// Cerca ed apre la porta seriale
		try {
			tSerialPortIdentifier = CommPortIdentifier.getPortIdentifier(this.pConfiguration.getPortName());
			this.pSerialPort = (SerialPort)tSerialPortIdentifier.open(this.pConfiguration.getPortName(), this.pConfiguration.getTimeout());				
		}
		
		// Segnala eventuali errori legati all'impossibilità di trovare la porta seriale richiesta
		catch (NoSuchPortException e) { 
			this.loggerNotify(Type.Error, "Impossibile aprire la porta seriale <" + this.pConfiguration.getPortName() + ">", e.getMessage(), Verbosity.Low);				
			tOk = false;
		}
		
		// Segnala eventuali errori legati all'impossibilità di utilizzare la porta seriale richiesta
		catch (PortInUseException e) { 
			this.loggerNotify(Type.Error, "La porta seriale <" + this.pConfiguration.getPortName() + "> è già in uso", e.getMessage(), Verbosity.Low);				 					 
			tOk = false;
		}
		
		// Segnala eventuali altri errori
		catch(Exception e) {
			this.loggerNotify(Type.Error, "Si sono verificati dei problemi durante l'apertura della porta seriale <" + this.pConfiguration.getPortName() + ">", e.getMessage(), Verbosity.Low);				 					 
			tOk = false;			
		}
		
		// Prova ad impostare i parametri della porta che è stata aperta
		if(tOk){
			
			// Aggiunge una notifica nel log			
			tParamString += "Baud rate: " + this.pConfiguration.getBaudRate() + ",";
			tParamString += "Bit di dati:" + this.pConfiguration.getDataBits() + ",";
			tParamString += "Bit di stop:" + this.pConfiguration.getStopBits() + ",";
			tParamString += "Parità:" + this.pConfiguration.getParity() + ",";			
			
			this.loggerNotify(Type.Information, "Inizializzazione della porta seriale <" + this.pConfiguration.getPortName() + "> in corso.", tParamString, Verbosity.High);			
			
			// Prova ad impostare i parametri della porta
			try{
				this.pSerialPort.setSerialPortParams(this.pConfiguration.getBaudRate(), this.pConfiguration.getDataBits(),
						this.pConfiguration.getStopBits(), this.pConfiguration.getParity());
			}
			
			// Segnala eventuali errori
			catch(UnsupportedCommOperationException parException){
				this.loggerNotify(Type.Error, "Impossibile inizializzare la porta seriale <" + this.pConfiguration.getPortName() + ">", Verbosity.Low);				
				tOk = false;
			}
		}
		
		// Se l'apertura e l'inizializzazione della porta sono andate a buon fine..
		if (tOk) {
		
			// .. inizializza i canali di input e di output
			try {
				this.setInputStream(this.pSerialPort.getInputStream());
				this.setOutputStream(this.pSerialPort.getOutputStream());
			}
			
			// .. e segnala eventuali errori
			catch(IOException e) {
				this.loggerNotify(Type.Error, "Impossibile accedere agli stream di IO della porta seriale <" + this.pConfiguration.getPortName() + ">", Verbosity.Low);				
				tOk = false;
			}
		}
		
		// Si collega all'evento di notifica della ricezione di bytes sulla porta seriale
		if(tOk && this.getEventBasedInput()) 
		{
			this.pSerialPort.notifyOnDataAvailable(true);
			
			// Istanzia il Listner
			this.pSerialPortListener = new SerialPortEventListener() {				
				public void serialEvent(SerialPortEvent arg0) {					
					receive();
				}
			};
			
			// Collega il listener
			try {
				this.pSerialPort.addEventListener(this.pSerialPortListener);
			}
			catch(TooManyListenersException e) { 
				this.loggerNotify(Type.Error, "Impossiible regisrare un listener per ricevere gli eventi dalla porta seriale", Verbosity.Low);
				tOk  = false;
			}
		}
		
		// Se si sono verificati errori, chiude ciò che è rimasto aperto
		if (!tOk) {
			this.pFreeChannel();
			this.setInputStream(null);
			this.setOutputStream(null);
		}

		tSerialPortIdentifier = null;
	}
	
	
	/**
	 * Funzione di chiusura del canale
	 */
	protected void closeChannel() {		
		this.pFreeChannel();		
	}
	
	
	/**
	 * Funzione per chiudere gli stream aperti e la porta seriale
	 */
	private void pFreeChannel() {

		// Rilascia il puntatore al Listener
		if(this.pSerialPort != null && this.pSerialPortListener != null) {
			this.pSerialPort.removeEventListener();
			this.pSerialPortListener = null;
		}
		
		// Chiude lo stream di input se aperto 
		if(this.getInputStream() != null) {
			try { this.getInputStream().close(); }
			catch(Exception e) { }			
		}
				
		// Chiude lo stream di ouput, se aperto
		if(this.getOutputStream() != null) {
			try { this.getOutputStream().close(); }
			catch(Exception e) { }			
		}
				
		// Chiude la porta seriale se aperta 
		if(this.pSerialPort != null) {
			try { this.pSerialPort.close(); }
			catch (Exception e) { }
			this.pSerialPort = null;
		}
	}
}
